package sob

import (
	"encoding/binary"
	"fmt"
	"math"
)

const (
	tByte = iota + 1
	tByte2
	tByte4
	tByte8
	tBytes1
	tBytes2
	tBytes4
)

type Data struct {
	Field  string
	Value  any
	vType  byte
	vBytes []byte
}

func (d *Data) encode() ([]byte, error) {
	fl := len(d.Field)
	if fl > 255 {
		return nil, fmt.Errorf("field too long")
	}
	field := make([]byte, fl+1)
	field[0] = byte(fl)
	copy(field[1:], d.Field)

	var value []byte
	switch v := d.Value.(type) {
	case int:
		value = int2b(int64(v))
	case int8:
		value = int2b(int64(v))
	case int16:
		value = int2b(int64(v))
	case int32:
		value = int2b(int64(v))
	case int64:
		value = int2b(v)
	case uint:
		value = uint2b(uint64(v))
	case uint8:
		value = uint2b(uint64(v))
	case uint16:
		value = uint2b(uint64(v))
	case uint32:
		value = uint2b(uint64(v))
	case uint64:
		value = uint2b(v)
	case float64:
		value = float2b(v)
	case bool:
		value = bool2b(v)
	case []byte:
		value = bytes2b(v)
	case string:
		value = bytes2b([]byte(v))
	default:
		return nil, fmt.Errorf("unsupported field type")
	}

	return append(field, value...), nil
}

func (d *Data) decode(b []byte) (int, error) {
	l := len(b)
	n := 0
	if l < 1 {
		return 0, fmt.Errorf("length error")
	}
	fl := b[0] // field length
	n += 1
	if l < n+int(fl) {
		return 0, fmt.Errorf("length error")
	}
	d.Field = string(b[n : n+int(fl)])
	n += int(fl)
	if l < n+1 {
		return 0, fmt.Errorf("length error")
	}
	d.vType = b[n]
	n += 1

	rl := 0 // read length
	switch d.vType {
	case tByte:
		rl = 1
	case tByte2:
		rl = 2
	case tByte4:
		rl = 4
	case tByte8:
		rl = 8
	case tBytes1:
		if l < n+1 {
			return 0, fmt.Errorf("length error")
		}
		rl = int(b[n])
		n += 1
	case tBytes2:
		if l < n+2 {
			return 0, fmt.Errorf("length error")
		}
		rl = int(binary.BigEndian.Uint16(b[n : n+2]))
		n += 2
	case tBytes4:
		if l < n+4 {
			return 0, fmt.Errorf("length error")
		}
		rl = int(binary.BigEndian.Uint32(b[n : n+4]))
		n += 4
	default:
		return 0, fmt.Errorf("unknown value type")
	}
	if l < n+rl {
		return 0, fmt.Errorf("length error")
	}
	d.vBytes = b[n : n+rl]
	n += rl
	return n, nil
}

func (d *Data) Int() (int, error) {
	n, err := d.Int64()
	if err != nil {
		return 0, err
	}
	return int(n), nil
}

func (d *Data) Int8() (int8, error) {
	n, err := d.Int64()
	if err != nil {
		return 0, err
	}
	if n < math.MinInt8 || n > math.MaxInt8 {
		return 0, fmt.Errorf("out of range")
	}
	return int8(n), nil
}

func (d *Data) Int16() (int16, error) {
	n, err := d.Int64()
	if err != nil {
		return 0, err
	}
	if n < math.MinInt16 || n > math.MaxInt16 {
		return 0, fmt.Errorf("out of range")
	}
	return int16(n), nil
}

func (d *Data) Int32() (int32, error) {
	n, err := d.Int64()
	if err != nil {
		return 0, err
	}
	if n < math.MinInt32 || n > math.MaxInt32 {
		return 0, fmt.Errorf("out of range")
	}
	return int32(n), nil
}

func (d *Data) Int64() (int64, error) {
	n, err := d.Uint64()
	if err != nil {
		return 0, err
	}
	return int64(n), nil
}

func (d *Data) Uint() (uint, error) {
	n, err := d.Uint64()
	if err != nil {
		return 0, err
	}
	return uint(n), nil
}

func (d *Data) Uint8() (uint8, error) {
	n, err := d.Uint64()
	if err != nil {
		return 0, err
	}
	if n > math.MaxUint8 {
		return 0, fmt.Errorf("out of range")
	}
	return uint8(n), nil
}

func (d *Data) Uint16() (uint16, error) {
	n, err := d.Uint64()
	if err != nil {
		return 0, err
	}
	if n > math.MaxUint16 {
		return 0, fmt.Errorf("out of range")
	}
	return uint16(n), nil
}

func (d *Data) Uint32() (uint32, error) {
	n, err := d.Uint64()
	if err != nil {
		return 0, err
	}
	if n > math.MaxUint32 {
		return 0, fmt.Errorf("out of range")
	}
	return uint32(n), nil
}

func (d *Data) Uint64() (uint64, error) {
	switch d.vType {
	case tByte:
		return uint64(d.vBytes[0]), nil
	case tByte2:
		return uint64(binary.BigEndian.Uint16(d.vBytes)), nil
	case tByte4:
		return uint64(binary.BigEndian.Uint32(d.vBytes)), nil
	case tByte8:
		return binary.BigEndian.Uint64(d.vBytes), nil
	default:
		return 0, fmt.Errorf("value type error")
	}
}

func (d *Data) Float64() (float64, error) {
	if d.vType != tByte8 {
		return 0, fmt.Errorf("value type error")
	}
	return math.Float64frombits(binary.BigEndian.Uint64(d.vBytes)), nil
}

func (d *Data) Bool() (bool, error) {
	if d.vType != tByte {
		return false, fmt.Errorf("value type error")
	}
	return d.vBytes[0] == 1, nil
}

func (d *Data) Bytes() ([]byte, error) {
	switch d.vType {
	case tBytes1, tBytes2, tBytes4:
		return d.vBytes, nil
	default:
		return nil, fmt.Errorf("value type error")
	}
}

func (d *Data) String() (string, error) {
	b, err := d.Bytes()
	if err != nil {
		return "", err
	}
	return string(b), nil
}

func int2b(i int64) []byte {
	if i >= math.MinInt8 && i <= math.MaxInt8 {
		return []byte{tByte, byte(i)}
	}
	if i >= math.MinInt16 && i <= math.MaxInt16 {
		b := make([]byte, 3)
		b[0] = tByte2
		binary.BigEndian.PutUint16(b[1:], uint16(i))
		return b
	}
	if i >= math.MinInt32 && i <= math.MaxInt32 {
		b := make([]byte, 5)
		b[0] = tByte4
		binary.BigEndian.PutUint32(b[1:], uint32(i))
		return b
	}
	b := make([]byte, 9)
	b[0] = tByte8
	binary.BigEndian.PutUint64(b[1:], uint64(i))
	return b
}

func uint2b(u uint64) []byte {
	if u <= math.MaxUint8 {
		return []byte{tByte, byte(u)}
	}
	if u <= math.MaxUint16 {
		b := make([]byte, 3)
		b[0] = tByte2
		binary.BigEndian.PutUint16(b[1:], uint16(u))
		return b
	}
	if u <= math.MaxUint32 {
		b := make([]byte, 5)
		b[0] = tByte4
		binary.BigEndian.PutUint32(b[1:], uint32(u))
		return b
	}
	b := make([]byte, 9)
	b[0] = tByte8
	binary.BigEndian.PutUint64(b[1:], u)
	return b
}

func float2b(v float64) []byte {
	b := make([]byte, 9)
	b[0] = tByte8
	binary.BigEndian.PutUint64(b[1:], math.Float64bits(v))
	return b
}

func bool2b(v bool) []byte {
	var i byte
	if v {
		i = 1
	}
	return []byte{tByte, i}
}

func bytes2b(v []byte) []byte {
	n := len(v)
	if n <= math.MaxUint8 {
		b := make([]byte, n+2)
		b[0] = tBytes1
		b[1] = byte(n)
		copy(b[2:], v)
		return b
	}
	if n <= math.MaxUint16 {
		b := make([]byte, n+3)
		b[0] = tBytes2
		binary.BigEndian.PutUint16(b[1:3], uint16(n))
		copy(b[3:], v)
		return b
	}
	b := make([]byte, n+5)
	b[0] = tBytes4
	binary.BigEndian.PutUint32(b[1:5], uint32(n))
	copy(b[5:], v)
	return b
}
